﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="PowerPointEngine.cs" company="arvystate.net">
//   arvystate.net 
// </copyright>
// <summary>
//   Engine works directly with open PowerPoint instance
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace PresentationAid.Lib.GestureEngines
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using Microsoft.Office.Core;
    using Microsoft.Office.Interop.PowerPoint;

    using NLog;

    using PresentationAid.Lib.Enums;
    using PresentationAid.Lib.GestureEngines.GestureSettings;

    /// <summary>
    /// Engine works directly with opened PowerPoint instance
    /// </summary>
    public sealed class PowerPointEngine : GestureEngine
    {
        #region Static Fields

        /// <summary>
        /// The logger.
        /// </summary>
        private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

        #endregion

        #region Fields

        /// <summary>
        /// Gesture command mapping
        /// </summary>
        private readonly Dictionary<GestureCommand, SlideShowCommand> keyMapping;

        /// <summary>
        /// PowerPoint application instance.
        /// </summary>
        private readonly Application powerPointInstance;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="PowerPointEngine"/> class.
        /// </summary>
        public PowerPointEngine()
        {
            Logger.Trace("Init: PowerPointEngine");

            this.keyMapping = new Dictionary<GestureCommand, SlideShowCommand>();

            // Connect to powerpoint
            this.powerPointInstance = new Application();

            Logger.Trace("InitDetail: PowerPointEngine - CreateSettings");

            this.EngineSettings = new PowerPointSettings(this);
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets a value indicating whether if SlideShow is in progress
        /// </summary>
        public override bool IsInSlideshow
        {
            get
            {
                try
                {
                    return this.powerPointInstance.SlideShowWindows.Count > 0;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether if PowerPoint instance is visible
        /// </summary>
        public bool IsRunning
        {
            get
            {
                try
                {
                    return this.powerPointInstance.Visible == MsoTriState.msoTrue;
                }
                catch (Exception)
                {
                    return false;
                }
            }
        }

        /// <summary>
        /// Gets Microsoft Office Version
        /// </summary>
        public string OfficeVersion
        {
            get
            {
                try
                {
                    switch (this.powerPointInstance.Version)
                    {
                        case "7.0":
                            return "95";
                        case "8.0":
                            return "97";
                        case "9.0":
                            return "2000";
                        case "10.0":
                            return "2002";
                        case "11.0":
                            return "2003";
                        case "12.0":
                            return "2007";
                        case "14.0":
                            return "2010";
                        case "15.0":
                            return "2013";
                        default:
                            return "Unknown";
                    }
                }
                catch (Exception)
                {
                    return "None";
                }
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Clears currently mapped command
        /// </summary>
        /// <param name="gesture">Gesture to clear
        /// </param>
        public void ClearMapping(GestureCommand gesture)
        {
            if (this.keyMapping.ContainsKey(gesture))
            {
                Logger.Debug("Method: ClearMapping - Gesture: {0}", gesture.ToString());

                this.keyMapping.Remove(gesture);
            }
        }

        /// <summary>
        /// Returns slideshow command that is mapped to gesture
        /// </summary>
        /// <param name="gesture">Desired gesture
        /// </param>
        /// <returns>
        /// Slideshow command that is mapped to gesture <see cref="SlideShowCommand"/>.
        /// </returns>
        public SlideShowCommand GetMapping(GestureCommand gesture)
        {
            return this.keyMapping.ContainsKey(gesture) ? this.keyMapping[gesture] : SlideShowCommand.None;
        }

        /// <summary>
        /// Returns gesture that is mapped to selected PowerPoint command
        /// </summary>
        /// <param name="command">Slideshow command
        /// </param>
        /// <returns>
        /// Gesture that is mapped to slideshow command <see cref="GestureCommand"/>.
        /// </returns>
        public GestureCommand GetMapping(SlideShowCommand command)
        {
            if (this.keyMapping.ContainsValue(command))
            {
                Logger.Debug("Method: GetMapping - Command: {0}", command.ToString());

                foreach (KeyValuePair<GestureCommand, SlideShowCommand> pair in this.keyMapping)
                {
                    if (pair.Value == command)
                    {
                        Logger.Trace("MethodDetail: GetMapping - Return: {0}", pair.Key);

                        return pair.Key;
                    }
                }
            }

            return GestureCommand.None;
        }

        /// <summary>
        /// Returns mapping in string format
        /// </summary>
        /// <returns>
        /// The <see cref="string"/>.
        /// </returns>
        public override string GetMapping()
        {
            StringBuilder mapping = new StringBuilder();

            // Add control gesture
            mapping.Append(this.ControlGesture);
            mapping.Append(":TakeControl;");

            foreach (KeyValuePair<GestureCommand, SlideShowCommand> pair in this.keyMapping)
            {
                if (pair.Key == this.ControlGesture)
                {
                    continue;
                }

                mapping.Append(pair.Key + ":" + pair.Value + ";");
            }

            // Remove last semicolon
            mapping.Remove(mapping.Length - 1, 1);

            Logger.Debug("Method: GetMapping - Return: {0}", mapping.ToString());

            return mapping.ToString();
        }

        /// <summary>
        /// PowerPoint Engine has easy job, we are resolving SlideShow command from already mapped dictionary
        /// </summary>
        /// <param name="gesture">Mapped gesture
        /// </param>
        /// <returns>
        /// Command that gesture is mapped to <see cref="SlideShowCommand"/>.
        /// </returns>
        public override SlideShowCommand GetSlideShowCommand(GestureCommand gesture)
        {
            return this.GetMapping(gesture);
        }

        /// <summary>
        /// Parses mapping from string
        /// </summary>
        /// <param name="map">Command mapping as a string
        /// </param>
        /// <returns>
        /// True on success <see cref="bool"/>.
        /// </returns>
        public override bool ParseMapping(string map = null)
        {
            if (!string.IsNullOrEmpty(map))
            {
                Logger.Debug("Method: ParseMapping - Map: {0}", map);

                try
                {
                    this.keyMapping.Clear();

                    string[] mappings = map.Split(';');

                    foreach (string[] powerPointCommand in mappings.Select(command => command.Split(':')))
                    {
                        if (powerPointCommand[1].Trim() == "TakeControl")
                        {
                            this.ControlGesture =
                                (GestureCommand)Enum.Parse(typeof(GestureCommand), powerPointCommand[0]);
                        }
                        else
                        {
                            this.SetMapping(
                                (GestureCommand)Enum.Parse(typeof(GestureCommand), powerPointCommand[0]), 
                                (SlideShowCommand)Enum.Parse(typeof(SlideShowCommand), powerPointCommand[1]));
                        }
                    }

                    return true;
                }
                catch (Exception ex)
                {
                    Logger.Warn("Method: ParseMapping - Exception: {0}", ex.Message);
                }
            }

            this.SetDefaultMapping();

            return false;
        }

        /// <summary>
        /// Sets default command mapping for PowerPoint engine
        /// </summary>
        public override void SetDefaultMapping()
        {
            Logger.Debug("Method: SetDefaultMapping");

            base.SetDefaultMapping();

            this.SetMapping(GestureCommand.Wave, SlideShowCommand.StartStopSlideshow);
            this.SetMapping(GestureCommand.SwipeRight, SlideShowCommand.NextSlide);
            this.SetMapping(GestureCommand.SwipeLeft, SlideShowCommand.PreviousSlide);
        }

        /// <summary>
        /// Sets gesture mapping to desired PowerPoint command
        /// </summary>
        /// <param name="gesture">Desired gesture
        /// </param>
        /// <param name="pptCommand">Desired command
        /// </param>
        public void SetMapping(GestureCommand gesture, SlideShowCommand pptCommand)
        {
            Logger.Debug(
                "Method: SetMapping - Gesture: {0}, PptCommand: {1}", gesture.ToString(), pptCommand.ToString());

            if (pptCommand == SlideShowCommand.None)
            {
                this.ClearMapping(gesture);
            }

            if (this.keyMapping.ContainsKey(gesture))
            {
                this.keyMapping[gesture] = pptCommand;
            }
            else
            {
                this.keyMapping.Add(gesture, pptCommand);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Executes PowerPoint specific gestures
        /// </summary>
        /// <param name="gesture">Gesture to execute
        /// </param>
        /// <returns>
        /// True on success <see cref="bool"/>.
        /// </returns>
        protected override bool ExecuteSpecificGesture(GestureCommand gesture)
        {
            try
            {
                if (this.keyMapping.ContainsKey(gesture))
                {
                    Logger.Trace("Method: ExecuteSpecificGesture: Gesture: {0}", gesture.ToString());

                    switch (this.keyMapping[gesture])
                    {
                        case SlideShowCommand.StartStopSlideshow:
                            if (!this.IsInSlideshow)
                            {
                                // Activate PowerPoint window if it is not active
                                if (this.powerPointInstance.Active != MsoTriState.msoTrue)
                                {
                                    this.powerPointInstance.Activate();
                                }

                                // Check if PowerPoint has an active presentation that can be opened
                                if (this.powerPointInstance.Presentations.Count > 0)
                                {
                                    Logger.Trace("MethodDetail: ExecuteSpecificGesture - SlideShowRun");

                                    this.powerPointInstance.ActivePresentation.SlideShowSettings.Run();
                                }
                            }
                            else
                            {
                                Logger.Trace("MethodDetail: ExecuteSpecificGesture - SlideShowEnd");

                                this.powerPointInstance.ActivePresentation.SlideShowWindow.View.Exit();
                            }

                            return true;
                        case SlideShowCommand.NextSlide:
                            if (this.IsInSlideshow)
                            {
                                Logger.Trace("MethodDetail: ExecuteSpecificGesture - NextSlide");

                                this.powerPointInstance.ActivePresentation.SlideShowWindow.View.Next();

                                return true;
                            }

                            Logger.Trace("MethodDetail: ExecuteSpecificGesture - NextSlideFail");

                            return false;
                        case SlideShowCommand.PreviousSlide:
                            if (this.IsInSlideshow)
                            {
                                Logger.Trace("MethodDetail: ExecuteSpecificGesture - PreviousSlide");

                                this.powerPointInstance.ActivePresentation.SlideShowWindow.View.Previous();

                                return true;
                            }

                            Logger.Trace("MethodDetail: ExecuteSpecificGesture - PreviousSlideFail");

                            return false;
                    }
                }

                return false;
            }
            catch (Exception)
            {
                return false;
            }
        }

        #endregion
    }
}